package net.mof.manual

import com.squareup.javapoet.TypeName
import net.mof.manual.Utils.arrayListClassName
import net.mof.manual.Utils.hashMapClassName
import net.mof.manual.Utils.listClassName
import net.mof.manual.Utils.mapClassName
import net.mof.manual.Utils.stringClassName
import net.mof.manual.meta.*
import net.mof.manual.sub.SubType
import net.mof.manual.sub.SubTypeMap
import java.io.IOException
import java.util.*
import java.util.function.BiConsumer
import java.util.function.Consumer
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.*
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.MirroredTypeException
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.SimpleAnnotationValueVisitor6
import javax.lang.model.util.Types
import javax.tools.Diagnostic
import kotlin.reflect.KClass

/**
 *
 *注解解析的主要类
 *
 * @auth LiYueHui
 * @date 2019/9/10
 */
@SupportedAnnotationTypes("net.mof.manual.JsonMark", "net.mof.manual.ManualJsonProperty")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class ManualProcessor : AbstractProcessor() {

    var messager: Messager? = null
    var filer: Filer? = null
    var types: Types? = null

    @Synchronized
    override fun init(processingEnvironment: ProcessingEnvironment) {
        super.init(processingEnvironment)
        messager = processingEnvironment.messager
        filer = processingEnvironment.filer
        types = processingEnvironment.typeUtils
    }

    override fun process(set: Set<TypeElement>,
                         roundEnvironment: RoundEnvironment): Boolean {
        val typeMap: MutableMap<TypeElement, MarkedClass> = HashMap()

        parseMarkedClass(roundEnvironment, typeMap)
        find(typeMap)
        val typeElements: Set<TypeElement> = typeMap.keys
        val iterator = typeElements.iterator()
        while (iterator.hasNext()) {
            val next = iterator.next()
            val bean = typeMap[next]
            val javaFile = bean?.brewMarkedClass()
            try {
                javaFile?.writeTo(filer)
            } catch (e: IOException) {
                e.printStackTrace()
                messager!!.printMessage(Diagnostic.Kind.ERROR,
                        "ManualJson: write file error!")
            }
        }
        return true
    }

    private fun parseMarkedClass(roundEnvironment: RoundEnvironment,
                                 typeElementMarkedClassMap: MutableMap<TypeElement, MarkedClass>) {
        val elementsAnnotatedWith = roundEnvironment
                .getElementsAnnotatedWith(ManualJsonProperty::class.java)
        val iterator: Iterator<Element> = elementsAnnotatedWith.iterator()
        while (iterator.hasNext()) {
            val element = iterator.next()
            val annotation = element
                    .getAnnotation(ManualJsonProperty::class.java)
            val typeElement = element
                    .enclosingElement as TypeElement
            var markedClass = typeElementMarkedClassMap[typeElement]
            if (markedClass == null) {
                markedClass = MarkedClass()
                // inner class
                val packageElement = typeElement.enclosingElement
                if (packageElement.kind != ElementKind.PACKAGE) {
                    val topPackageElement = packageElement
                            .enclosingElement
                    // Verification: 1 nesting,只能嵌套一次
                    if (topPackageElement.kind != ElementKind.PACKAGE) {
                        messager?.printMessage(Diagnostic.Kind.ERROR,
                                "ManualJson: inner class can only be nested once")
                    }
                    markedClass.packageName = topPackageElement.asType()
                            .toString()
                    markedClass.markedClassInfo = MarkedClassInfo(packageElement
                            .simpleName.toString(), typeElement
                            .simpleName.toString())
                } else {
                    markedClass.packageName = packageElement.asType().toString()
                    val className = typeElement.simpleName.toString()
                    markedClass.markedClassInfo = MarkedClassInfo(null, className)
                    // messager.printMessage(Diagnostic.Kind.NOTE,
                    // "jsonBean.packageName:" +
                    // jsonBean.packageName + " className:" + className);
                }
                typeElementMarkedClassMap[typeElement] = markedClass
                // Verification: Class value should be annotated with @JsonMark
                val manualJsonProperty = typeElement
                        .getAnnotation(JsonMark::class.java)
                if (manualJsonProperty == null) {
                    messager?.printMessage(
                            Diagnostic.Kind.ERROR,
                            "ManualJson: Class \""
                                    + markedClass.markedClassInfo?.getClassName()
                                    + "\" need add @JsonMark annotation!!")
                }
                // subtype
                val subTypeMap = typeElement
                        .getAnnotation(SubTypeMap::class.java)
                if (subTypeMap != null) {
                    val values: Array<out SubType> = subTypeMap.value
                    for (i in values.indices) {
                        val subType = values[i]
                        val name = subType.name
                        try {
                            val aClass: KClass<*> = subType.value
                            messager?.printMessage(Diagnostic.Kind.ERROR,
                                    "ManualJson: get class Success")
                        } catch (mte: MirroredTypeException) {
                            val typeName = TypeName.get(mte
                                    .typeMirror)
                            markedClass.subTypeMap.put(name, typeName)
                        }
                    }
                }
            }
            var metaInfoBase: BaseFieldMetaInfo? = null
            //基本数据类型
            messager?.printMessage(Diagnostic.Kind.NOTE, "解析字段:$element 类型 " + element.asType())
            if (element.asType().kind.isPrimitive) {
                metaInfoBase = PrimitiveFieldMetaInfo()
            } else if (element.asType().kind == TypeKind.DECLARED) {
                val tElement = (element
                        .asType() as DeclaredType).asElement() as TypeElement
                if (stringClassName.toString() ==
                        tElement.qualifiedName.toString()) {
                    metaInfoBase = StringFieldMetaInfo()
                } else if (tElement.getAnnotation(JsonMark::class.java) != null) {
                    metaInfoBase = JsonMarkFieldMetaInfo()
                } else { // serializer 序列化
                    if (element is VariableElement
                            && element.getAnnotation(ManualSerializer::class.java) != null
                            && element.getAnnotation(ManualDeserializer::class.java) != null) {
                        metaInfoBase = SerializerFeildMetaInfo()
                        val serializer = element
                                .getAnnotation(ManualSerializer::class.java)
                        try {
                            val value: KClass<*> = serializer.value
                            messager?.printMessage(Diagnostic.Kind.ERROR,
                                    "ManualJson: get class Success")
                        } catch (e: MirroredTypeException) {
                            (metaInfoBase as SerializerFeildMetaInfo?)?.serializerTypeName = TypeName
                                    .get(e.typeMirror)
                        }
                        val deserializer = element
                                .getAnnotation(ManualDeserializer::class.java)
                        try {
                            val value: KClass<*> = deserializer.value
                            messager?.printMessage(Diagnostic.Kind.ERROR,
                                    "ManualJson: get class Success")
                        } catch (e: MirroredTypeException) {
                            (metaInfoBase as SerializerFeildMetaInfo?)?.deserializerTypeName = TypeName
                                    .get(e.typeMirror)
                        }
                        (metaInfoBase as SerializerFeildMetaInfo?)?.typeName = TypeName
                                .get(element.asType())
                    } else {
                        val typeParameters = tElement
                                .typeParameters
                        if (typeParameters != null && typeParameters.size > 0) { // list
                            if (listClassName.toString() ==
                                    tElement.toString() || arrayListClassName.toString() ==
                                    tElement.toString()) {
                                val variableElement = element as VariableElement
                                val declaredType = variableElement
                                        .asType() as DeclaredType
                                metaInfoBase = ListFieldMetaInfo()
                                val typeMirror = declaredType
                                        .typeArguments[0]
                                (metaInfoBase as ListFieldMetaInfo?)?.typeParamTypeMirror = typeMirror
                                // messager.printMessage(Diagnostic.Kind.NOTE,
                                // "typeParamTypeMirror:"+
                                // TypeName.get(typeMirror).toString());
                            } else if (mapClassName.toString() ==
                                    tElement.toString() || hashMapClassName.toString() ==
                                    tElement.toString()) { // map
                                metaInfoBase = MapFieldMetaInfo()
                            }
                        }
                    }
                }
            }
            if (metaInfoBase != null) {
                metaInfoBase.value = annotation.value
                metaInfoBase.element = element
                markedClass.baseFieldList.add(metaInfoBase)
            } else {
                messager?.printMessage(Diagnostic.Kind.ERROR,
                        "ManualJson: 字段‘$element’ metaInfo parse Error!")
            }
//           messager?.printMessage(Diagnostic.Kind.NOTE, "packageName:" +
//           metaInfo.value);
        }
    }

    private fun find(typeElementMarkedClassMap: Map<TypeElement, MarkedClass>) {
        val typeElements = typeElementMarkedClassMap.keys
        val iterator = typeElements.iterator()
        while (iterator.hasNext()) {
            val next = iterator.next()
            val markedClass = typeElementMarkedClassMap[next]
            markedClass?.baseFieldList?.forEach { metaInfoBase: BaseFieldMetaInfo? ->
                if (metaInfoBase is JsonMarkFieldMetaInfo) {
                    val typeElement = (metaInfoBase?.element?.asType() as DeclaredType).asElement() as TypeElement
                    (metaInfoBase as JsonMarkFieldMetaInfo?)?.jsonMarkField = typeElementMarkedClassMap[typeElement]
                }
            }

            if (markedClass != null) {
                findSuper(markedClass, next.superclass, typeElementMarkedClassMap)
            }
        }
    }

    private fun findSuper(markedClass: MarkedClass, typeMirror: TypeMirror,
                          typeElementMarkedClassMap: Map<TypeElement, MarkedClass>) {
        if (typeMirror == null || typeMirror.kind != TypeKind.DECLARED) return;

        val superElement = (typeMirror as DeclaredType)
                .asElement() as TypeElement
        val superJsonBean = typeElementMarkedClassMap[superElement]
        if (superJsonBean == null) return;
        markedClass?.superMarkedClass = superJsonBean
        findSuper(superJsonBean, superElement.superclass, typeElementMarkedClassMap)
    }

    internal inner class Visit(var map: Map<String?, String?>) : SimpleAnnotationValueVisitor6<Any?, Any?>() {
        override fun visitArray(list: List<AnnotationValue>, o: Any?): Any? {
            if (list != null && list.size > 0) {
                list.forEach(Consumer { annotationValue: AnnotationValue ->
                    messager!!.printMessage(Diagnostic.Kind.NOTE,
                            "ManualJson: Class $annotationValue")
                    annotationValue.accept<Any, Any?>(Visit(map), null)
                })
            }
            return null
        }

        override fun visitAnnotation(annotationMirror: AnnotationMirror,
                                     o: Any?): Any? {
            val elementValues = annotationMirror
                    .elementValues
            elementValues
                    .forEach(BiConsumer { executableElement: ExecutableElement, annotationValue: AnnotationValue ->
                        messager?.printMessage(
                                Diagnostic.Kind.NOTE,
                                "AnnotationValue "
                                        + annotationValue.value
                                        + "-------ExecutableElement:"
                                        + executableElement.simpleName)
                    })
            return super.visitAnnotation(annotationMirror, o)
        }

    }
}